package level.model.utility;

import java.awt.Dimension;
import java.awt.Point;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.io.File;
import java.util.ArrayList;
import java.util.TreeMap;

import com.infomatiq.jsi.Rectangle;
import com.infomatiq.jsi.rtree.RTree;

import level.editor.control.tile.TileConstants;
import level.model.Addable;
import level.model.Collision;
import level.model.GameObject;
import level.model.State;
import level.model.StateCollisionLayer;
import level.model.StateLayer;
import level.model.StateObjectLayer;
import level.model.StateTileLayer;
import level.model.Tile;

public class StateHandler
{
	public final static String DEFAULT_DIR = "resources/levels/default/";
	public final static String DEFAULT_FILENAME = "Untitled";
	public final static String DEFAULT_EXTENSION = ".xml";
	public final static String MODIFIED_PREFIX = "*";
	public static final String UNMODIFIED_PREFIX = "";

	public static State copy(State state)
	{
		State newState = new State();
		newState.setFile(state.getFile());
		TreeMap<Integer, StateLayer> newLayers = newState.getLayers();

		for (Integer i : state.getLayers().keySet())
		{
			newLayers.put(i, copy(state.getLayers().get(i)));
		}
		return newState;
	}

	public static void add(StateLayer layer, Addable addable)
	{
		if (layer instanceof StateTileLayer && addable instanceof Tile)
		{
			addTile((StateTileLayer) layer, (Tile) addable);
		}

		if (layer instanceof StateCollisionLayer
				&& addable instanceof Collision)
		{
			addCollision((StateCollisionLayer) layer, (Collision) addable);
		}

		if (layer instanceof StateObjectLayer && addable instanceof GameObject)
		{
			addGameObject((StateObjectLayer) layer, (GameObject) addable);
		}
	}

	public static State defaultState(State state, int num)
	{
		// Default parameters
		StateTileLayer foreground = new StateTileLayer("Foreground",
				new Integer(10));
		StateTileLayer background = new StateTileLayer("Background",
				new Integer(9));
		StateCollisionLayer collision = new StateCollisionLayer(
				StateSaver.COLLISION_LAYER_NODE_NAME, new Integer(101));
		StateObjectLayer object = new StateObjectLayer(
				StateSaver.OBJECT_LAYER_NODE_NAME, new Integer(102));
		state.getLayers().put(foreground.getZOrder(), foreground);
		state.getLayers().put(background.getZOrder(), background);
		state.getLayers().put(collision.getZOrder(), collision);
		state.getLayers().put(object.getZOrder(), object);
		state.setFile(new File(DEFAULT_DIR + DEFAULT_FILENAME + num
				+ DEFAULT_EXTENSION));
		return state;
	}

	public static Dimension getSize(StateLayer layer)
	{
		if (layer instanceof StateTileLayer)
			return getSize((StateTileLayer) layer);
		else if (layer instanceof StateCollisionLayer)
			return ((StateCollisionLayer) layer).getSize();
		else if (layer instanceof StateObjectLayer)
			return getSize((StateObjectLayer) layer);
		else
			return null;
	}

	public static Point getTopLeft(StateLayer layer)
	{
		if (layer instanceof StateTileLayer)
			return getTopLeft((StateTileLayer) layer);
		else if (layer instanceof StateCollisionLayer)
			return ((StateCollisionLayer) layer).getTopLeft();
		else if (layer instanceof StateObjectLayer)
			return getTopLeft((StateObjectLayer) layer);
		else
			return null;
	}

	private static void addCollision(StateCollisionLayer layer,
			Collision addable)
	{
		ArrayList<Collision> collisions = layer.getCollisions();
		Collision collision = addable.cloneCollision();
		if (collisions.size() != 0)
		{
			ArrayList<Integer> toRemove = new ArrayList<Integer>();
			for (int i = 0; i < collisions.size(); i++)
			{
				Collision oldCollision = collisions.get(i);

				if (areConnected(collision.getLine(), oldCollision.getLine()))
				{
					toRemove.add(i);
				}
			}
			if (toRemove.size() != 0)
			{
				for (int i = toRemove.size() - 1; i >= 0; i--)
				{
					collision = combineCollision(collision.getLine(),
							collisions.get((int) toRemove.get(i)).getLine());
					collisions.remove((int) toRemove.get(i));
				}
			}
		}
		collisions.add(collision);
	}

	private static Collision combineCollision(Line2D line1, Line2D line2)
	{
		Collision collision = new Collision();

		Point2D leftMost = new Point2D.Float();
		leftMost = (line1.getX1() < line1.getX2() ? line1.getP1() : line1
				.getP2());
		leftMost = (leftMost.getX() < line2.getX1() ? leftMost : line2.getP1());
		leftMost = (leftMost.getX() < line2.getX2() ? leftMost : line2.getP2());

		Point2D rightMost = new Point2D.Float();
		rightMost = (line1.getX1() < line1.getX2() ? line1.getP2() : line1
				.getP1());
		rightMost = (rightMost.getX() < line2.getX1() ? line2.getP1()
				: rightMost);
		rightMost = (rightMost.getX() < line2.getX2() ? line2.getP2()
				: rightMost);

		if (rightMost.getX() != leftMost.getX())
		{
			collision.setLine(new Line2D.Float(leftMost, rightMost));
			return collision;
		}
		else
		{
			Point2D topMost = new Point2D.Float();
			topMost = (line1.getY1() < line1.getY2() ? line1.getP1() : line1
					.getP2());
			topMost = (topMost.getY() < line2.getY1() ? topMost : line2.getP1());
			topMost = (topMost.getY() < line2.getY2() ? topMost : line2.getP2());

			Point2D bottomMost = new Point2D.Float();
			bottomMost = (line1.getY1() < line1.getY2() ? line1.getP2() : line1
					.getP1());
			bottomMost = (bottomMost.getY() < line2.getY1() ? line2.getP1()
					: bottomMost);
			bottomMost = (bottomMost.getY() < line2.getY2() ? line2.getP2()
					: bottomMost);

			collision.setLine(new Line2D.Float(topMost, bottomMost));
			return collision;
		}
	}

	private static boolean areConnected(Line2D line1, Line2D line2)
	{
		if (getSlope(line1).equals(getSlope(line2)))
		{
			return line1.intersectsLine(line2);
		}
		else
			return false;
	}

	private static Double getSlope(Line2D line)
	{
		if (line != null)
		{
			double width = line.getX2() - line.getX1();
			double height = line.getY2() - line.getY1();

			if (width != 0)
				return new Double(height / width);
			else
				return new Double(Double.POSITIVE_INFINITY);
		}
		else
		{
			System.out.println("Error in StateHandler.getSlope");
			return null;
		}
	}

	private static void addTile(StateTileLayer layer, Tile t)
	{
		TreeMap<Integer, Tile> tiles = (TreeMap<Integer, Tile>) layer
				.getTiles();
		RTree tree = layer.getTree();
		Tile tile = t.cloneTile();
		if (!layer.isEmpty())   // This block of code checks for tiles on top of
								// one another and deletes the underlying tile
								// in case of stacking.
		{
			int toRemove = -1;
			for (int i : tiles.keySet())
			{
				if (tiles.get(i).getX() == tile.getX()
						&& tiles.get(i).getY() == tile.getY())
				{
					toRemove = i;
				}
			}
			if (toRemove >= 0)
			{
				tree.delete(new Rectangle(tiles.get(toRemove).getX(), tiles
						.get(toRemove).getY(), tiles.get(toRemove).getX()
						+ TileConstants.TILE_SIZE - 1, tiles.get(toRemove).getY()
						+ TileConstants.TILE_SIZE - 1), toRemove);
				tiles.remove(toRemove);
			}
		}
		if (!tiles.isEmpty())
			tiles.put(tiles.lastKey() + 1, tile);
		else
			tiles.put(0, tile);
		tree.add(
				new Rectangle(tiles.get(tiles.lastKey()).getX(), tiles.get(
						tiles.lastKey()).getY(), tiles.get(tiles.lastKey())
						.getX() + TileConstants.TILE_SIZE - 1, tiles.get(
						tiles.lastKey()).getY()
						+ TileConstants.TILE_SIZE - 1), tiles.lastKey());
//		System.out.println(tiles.get(tiles.lastKey()).getX());
	}

	private static void addGameObject(StateObjectLayer layer, GameObject g)
	{
		ArrayList<GameObject> gs = layer.getObjects();
		GameObject gObject = g.cloneObject();
		if (!layer.isEmpty()) // This block of code checks for tiles on top of
								// one another and deletes the underlying tile
								// in case of stacking.
		{
			int toRemove = -1;
			for (int i = 0; i < gs.size(); i++)
			{
				if (gs.get(i).getX() == gObject.getX()
						&& gs.get(i).getY() == gObject.getY())
				{
					toRemove = i;
				}
			}
			if (toRemove >= 0)
				gs.remove(toRemove);
		}
		gs.add(gObject);
	}

	private static StateLayer copy(StateLayer layer)
	{
		if (layer instanceof StateTileLayer)
			return copyTile((StateTileLayer) layer);
		else if (layer instanceof StateCollisionLayer)
			return copyCollision((StateCollisionLayer) layer);
		else if (layer instanceof StateObjectLayer)
			return copyObject((StateObjectLayer) layer);
		else
			return null;
	}

	private static StateTileLayer copyTile(StateTileLayer layer)
	{
		StateTileLayer newLayer = new StateTileLayer(layer.getName(),
				layer.getZOrder());
		if (!layer.isEmpty())
		{
			for (int i : layer.getTiles().keySet())
			{
				addTile(newLayer, layer.getTiles().get(i));
			}
		}
		return newLayer;
	}

	private static StateCollisionLayer copyCollision(StateCollisionLayer layer)
	{
		StateCollisionLayer newLayer = new StateCollisionLayer(layer.getName(),
				layer.getZOrder());
		if (!layer.isEmpty())
		{
			for (Collision coll : layer.getCollisions())
			{
				addCollision(newLayer, coll.cloneCollision());
			}
		}
		return newLayer;
	}

	private static StateObjectLayer copyObject(StateObjectLayer layer)
	{
		StateObjectLayer newLayer = new StateObjectLayer(layer.getName(),
				layer.getZOrder());
		if (!layer.isEmpty())
		{
			for (GameObject gameObject : layer.getObjects())
			{
				addGameObject(newLayer, gameObject.cloneObject());
			}
		}
		return newLayer;
	}

	private static Dimension getSize(StateTileLayer layer)
	{
		Dimension dim = null;
		Point tl = null;
		Point br = null;
		for (Tile tile : layer.getTiles().values())
		{
			if (tl == null)
				tl = new Point(tile.getX(), tile.getY());
			if (br == null)
				br = new Point(tile.getX() + tile.getW(), tile.getY()
						+ tile.getH());
			else
			{
				tl.x = Math.min(tile.getX(), tl.x);
				tl.y = Math.min(tile.getY(), tl.y);

				br.x = Math.max(tile.getX() + tile.getW(), br.x);
				br.y = Math.max(tile.getY() + tile.getH(), br.y);
			}
		}
		if (tl != null && br != null)
			dim = new Dimension(br.x - tl.x, br.y - tl.y);
		return dim;
	}

	private static Dimension getSize(StateObjectLayer layer)
	{
		Dimension dim = null;
		Point tl = null;
		Point br = null;
		for (GameObject g : layer.getObjects())
		{
			if (tl == null)
				tl = new Point(g.getX(), g.getY());
			if (br == null)
				br = new Point(g.getX() + g.getW(), g.getY() + g.getH());
			else
			{
				tl.x = Math.min(g.getX(), tl.x);
				tl.y = Math.min(g.getY(), tl.y);

				br.x = Math.max(g.getX() + g.getW(), br.x);
				br.y = Math.max(g.getY() + g.getH(), br.y);
			}
		}
		if (tl != null && br != null)
			dim = new Dimension(br.x - tl.x, br.y - tl.y);
		return dim;
	}

	private static Point getTopLeft(StateTileLayer layer)
	{
		Point tl = null;
		for (Tile tile : layer.getTiles().values())
		{
			if (tl == null)
				tl = new Point(tile.getX(), tile.getY());

			else
			{
				tl.x = Math.min(tile.getX(), tl.x);
				tl.y = Math.min(tile.getY(), tl.y);
			}
		}
		return tl;
	}

	private static Point getTopLeft(StateObjectLayer layer)
	{
		Point tl = null;
		for (GameObject g : layer.getObjects())
		{
			if (tl == null)
				tl = new Point(g.getX() - g.getW() / 2, g.getY() - g.getH() / 2);

			else
			{
				tl.x = Math.min(g.getX() - g.getW() / 2, tl.x);
				tl.y = Math.min(g.getY() - g.getH() / 2, tl.y);
			}
		}
		return tl;
	}
}
